package org.rms.cayenne.access;

import java.util.HashMap;

import org.rms.model.access.Persistable;
import org.rms.model.access.PersistableManager;
import org.rms.model.access.PersistableManagerFactory;

public class CayennePersistableManagerFactory implements PersistableManagerFactory {

    protected static final String PACKAGE = "ch.post.directfactory.cayenne";
    protected static final String MANAGERS_PACKAGE = PACKAGE + ".managers";

    private static InstantiatorMap<Persistable> instantiators = new InstantiatorMap<Persistable>();

    protected static class InstantiatorMap<T extends Persistable> extends
            HashMap<Class<? extends T>, Instantiator<? extends T>> {
        private static final long serialVersionUID = 1L;
    }

    public <T extends Persistable> PersistableManager<T> getPersistableManager(Class<T> modelClass) {

        try {
            synchronized (instantiators) {
                Instantiator<T> instantiator = (Instantiator<T>) instantiators.get(modelClass);
                if (instantiator == null) {
                    try {
                        final String managerClassName = MANAGERS_PACKAGE + "."
                                + modelClass.getSimpleName() + "Manager";
                        final Class<PersistableManager<T>> mgrClass = (Class<PersistableManager<T>>) Class
                                .forName(managerClassName);
                        instantiator = new ClassInstantiator<T>(mgrClass);

                    } catch (ClassNotFoundException e) {
                        instantiator = new GenericInstantiator<T>(getCayenneClass(modelClass));

                    }
                    instantiators.put(modelClass, instantiator);
                }
                return instantiator.newInstance();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @SuppressWarnings("unchecked")
    protected synchronized <T> Class<T> getCayenneClass(final Class<T> modelClass) {
        try {
            final String cayenneClassName = PACKAGE + "." + modelClass.getSimpleName();
            return (Class<T>) Class.forName(cayenneClassName);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException();
        }
    }

    protected static interface Instantiator<T extends Persistable> {
        PersistableManager<T> newInstance() throws Exception;
    }

    protected static class ClassInstantiator<T extends Persistable> implements Instantiator<T> {

        private Class<PersistableManager<T>> managerClass;

        public ClassInstantiator(final Class<PersistableManager<T>> mgrClass) {
            this.managerClass = mgrClass;
        }

        public PersistableManager<T> newInstance() throws Exception {
            return this.managerClass.newInstance();
        }

    }

    protected static class GenericInstantiator<T extends Persistable> implements Instantiator<T> {

        private Class<T> cayenneClass;

        public GenericInstantiator(final Class<T> cayenneClass) {
            this.cayenneClass = cayenneClass;
        }

        public PersistableManager<T> newInstance() throws Exception {
            return new CayenneObjectManager<T>(this.cayenneClass);
        }

    }

}
